﻿using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
//using System.Linq;
//using System.Text;
//using System.Threading.Tasks;

// AAA: Not working. Need some more debugging.
namespace GKS.DataTypes.Math.Numbers {
    public class IntegerNumber : Number {
        #region variables
        private List<long> internalNumbers;
        private Signs sign;
        private int representationBase;

        private static long internalBase = (long.MaxValue - 1) / 2;
        private static IntegerNumber zero = new IntegerNumber();
        private static IntegerNumber one = new IntegerNumber(1);
        #endregion

        #region properties
        public static Dictionary<char, int> CharValueMapping {
            get {
                return new Dictionary<char, int> {
                    { '0', 0 },
                    { '1', 1 },
                    { '2', 2 },
                    { '3', 3 },
                    { '4', 4 },
                    { '5', 5 },
                    { '6', 6 },
                    { '7', 7 },
                    { '8', 8 },
                    { '9', 9 },
                    { 'a', 10 },
                    { 'b', 11 },
                    { 'c', 12 },
                    { 'd', 13 },
                    { 'e', 14 },
                    { 'f', 15 },
                    { 'g', 16 },
                    { 'h', 17 },
                    { 'i', 18 },
                    { 'j', 19 },
                    { 'k', 20 },
                    { 'l', 21 },
                    { 'm', 22 },
                    { 'n', 23 },
                    { 'o', 24 },
                    { 'p', 25 },
                    { 'q', 26 },
                    { 'r', 27 },
                    { 's', 28 },
                    { 't', 29 },
                    { 'u', 30 },
                    { 'v', 31 },
                    { 'w', 32 },
                    { 'x', 33 },
                    { 'y', 34 },
                    { 'z', 35 }
                };
            }
        }

        public int RepresentationBase {
            get { return representationBase; }
            set {
                if (value < 2 || value > 36) {
                    throw new ArgumentOutOfRangeException(
                        "RepresentationBase",
                        "The indicated base is out of range. The range of the accepted values is [2, 36]");
                }

                representationBase = value;
            }
        }

        public static IntegerNumber Zero {
            get { return zero; }
        }

        public static IntegerNumber One {
            get { return one; }
        }
        #endregion

        #region enums

        #endregion

        #region constructors
        public IntegerNumber() {
            internalNumbers = new List<long>();
            internalNumbers.Add(0);
            sign = Signs.Positive;
        }

        public IntegerNumber(int value) {
            internalNumbers = new List<long>();

            if (value < 0) {
                sign = Signs.Negative;
                value = -value;
            } else {
                sign = Signs.Positive;
            }

            internalNumbers.Add(value);
        }

        public IntegerNumber(string value, int fromBase = 10) {
            if (!(new Regex(@"^[+-]{0,1}\d+$").IsMatch(value))) {
                throw new ArgumentException(
                    "The given value is not in a correct format. It should be the sign followed by the numbers (i.e. 123, -251, +20, etc...).",
                    "value");
            }

            char[] chars = value.ToCharArray();
            IntegerNumber multiplier = new IntegerNumber(1);
            int charIntValue;

            foreach (char c in chars) {
                charIntValue = CharValueMapping[c];

                if (charIntValue > fromBase) {
                    throw new ArgumentException(
                        string.Format("The given value is not in a correct format. There is a char not in the range [0, {0}]", fromBase),
                        "value");
                }

                MultiplyAbs(charIntValue).MultiplyAbs(multiplier);
                multiplier = multiplier.MultiplyAbs(fromBase);
            }

            //RepresentationBase = fromBase;
            //value = value.ToLower();
            //sign = Signs.Positive;
            
            //if (value.Contains("-")) {
            //    sign = Signs.Negative;
            //    value = value.Substring(1);
            //}

            //if (value.Contains("+")) {
            //    value = value.Substring(1);
            //}

            //char[] chars = value.ToCharArray();
            
            //internalNumbers = new List<long>();
            //internalNumbers.Add(0);

            //IntegerNumber multiplicator = One;
            //IntegerNumber temp = zero;
            //IntegerNumber baseNumber = new IntegerNumber(RepresentationBase);

            //for (int i = chars.Length - 1; i >= 0; i--) {
            //    temp += (new IntegerNumber(CharValueMapping[chars[i]]) * multiplicator);
            //    multiplicator *= baseNumber;
            //}
        }
        #endregion

        #region methods
        #region public
        public override Number Add(Number another) {
            if (another is IntegerNumber) {
                return this.Add((IntegerNumber)another);
            }

            throw new ArgumentException(
                "The given parameter is not an instance of IntegerNumber.",
                "another");
        }

        public IntegerNumber Add(IntegerNumber another) {
            IntegerNumber result;

            if (this.sign == another.sign) {
                result = this.AddAbs(another);
                result.sign = this.sign;
            } else {
                result = this.Subtract(another);
            }

            return result;
        }

        public override Number Subtract(Number another) {
            if (another is IntegerNumber) {
                return this.Subtract((IntegerNumber)another);
            }

            throw new ArgumentException(
                "The given parameter is not an instance of IntegerNumber.",
                "another");
        }

        public IntegerNumber Abs() {
            return new IntegerNumber() {
                sign = Signs.Positive,
                internalNumbers = this.internalNumbers
            };
        }

        public IntegerNumber Subtract(IntegerNumber another) {
            IntegerNumber result;

            if (this.sign == another.sign) {
                if (this.IsAbsSmallerThan(another)) {
                    result = another.SubtractAbs(this);
                    result.sign = this.sign == Signs.Positive
                                    ? Signs.Negative
                                    : Signs.Positive;
                } else {
                    result = this.SubtractAbs(another);
                    result.sign = this.sign;
                }
            } else {
                result = this.AddAbs(another);
                result.sign = this.sign;
            }

            return result;
        }

        public override Number Multiply(Number another) {
            if (another is IntegerNumber) {
                return this.Multiply((IntegerNumber)another);
            }

            throw new ArgumentException(
                "The given parameter is not an instance of IntegerNumber.",
                "another");
        }

        public IntegerNumber Multiply(IntegerNumber another) {
            IntegerNumber result = this.MultiplyAbs(another);
            result.sign = this.sign == another.sign 
                            ? Signs.Positive 
                            : Signs.Negative;

            return result;
        }

        public override Number Divide(Number another) {
            if (another is IntegerNumber) {
                return this.Divide((IntegerNumber)another);
            }

            throw new ArgumentException(
                "The given parameter is not an instance of IntegerNumber.",
                "another");
        }

        public IntegerNumber Divide(IntegerNumber another) {
            var temp = new IntegerNumber();

            return Divide(another, out temp);
        }

        public IntegerNumber Divide(IntegerNumber another, out IntegerNumber module) {
            if (another == Zero) {
                throw new DivideByZeroException("Attempted division by 0.");
            }
            
            IntegerNumber result = this.DivideAbs(another, out module);
            result.sign = this.sign == another.sign
                            ? Signs.Positive
                            : Signs.Negative;

            return result;
        }

        public override bool Equals(Number another) {
            return (another is IntegerNumber) ? Equals((IntegerNumber)another) : false;
        }

        public override bool Equals(object obj) {
            return obj is IntegerNumber ? (IntegerNumber)obj == (this) : false;
        }

        public bool Equals(IntegerNumber another) {
            bool result = this.sign == another.sign && this.internalNumbers.Count == another.internalNumbers.Count;

            for (int i = 0; result && i < internalNumbers.Count; i++) {
                result = result && (internalNumbers[i] == another.internalNumbers[i]);
            }

            return result;
        }

        public override string ToString(int representationBase) {
            throw new NotImplementedException();
        }

        public override string ToString() {
            return ToString(10);
        }
        #endregion

        #region private 
        private IntegerNumber AddAbs(IntegerNumber another) {
            IntegerNumber result = new IntegerNumber(0);
            int thisIndex = 0, 
                anotherIndex = 0;
            long temporarySum = 0, 
                excess = 0, 
                division = 0;

            // both arrays contain elements
            do {
                if (division > 0) {
                    result.internalNumbers.Add(0);
                }

                temporarySum = internalNumbers[thisIndex] + another.internalNumbers[anotherIndex] + division;
                division = temporarySum / internalBase;
                excess = temporarySum % internalBase;
                result.internalNumbers[result.internalNumbers.Count - 1] = excess;
                thisIndex++;
                anotherIndex++;
            } while (thisIndex < internalNumbers.Count && anotherIndex < internalNumbers.Count);

            // this array contains elements and the other does not
            while (thisIndex < internalNumbers.Count) {
                temporarySum = internalNumbers[thisIndex] + division;
                division = temporarySum / internalBase;
                excess = division > 0 ? temporarySum % internalBase : 0;
                result.internalNumbers.Add(excess);
                thisIndex++;
            }

            // this array does not contain elements and the other does
            while (anotherIndex < another.internalNumbers.Count) {
                temporarySum = another.internalNumbers[anotherIndex] + division;// excess;
                division = temporarySum / internalBase;
                excess = division > 0 ? temporarySum % internalBase : 0;

                result.internalNumbers.Add(excess);
                anotherIndex++;
            }

            if (division > 0) {
                result.internalNumbers.Add(division);
            }

            return result;
        }

        private IntegerNumber SubtractAbs(IntegerNumber another) {
            IntegerNumber result = new IntegerNumber();
            int index = 0;
            bool needToBorrow; 
            bool haveBorrowed = false;
            long temporarySubtraction = 0;

            if (this.IsAbsGreaterThan(another)) {
                do {
                    temporarySubtraction = this.internalNumbers[index] - (haveBorrowed ? 1 : 0);
                    needToBorrow = this.internalNumbers[index] < another.internalNumbers[index];
                    temporarySubtraction = this.internalNumbers[index] + (needToBorrow ? internalBase : 0);
                    haveBorrowed = needToBorrow;
                    temporarySubtraction -= another.internalNumbers[index];
                    result.internalNumbers.Add(temporarySubtraction);
                    index++;
                } while (index < another.internalNumbers.Count);

                while (index < this.internalNumbers.Count) {
                    temporarySubtraction = this.internalNumbers[index] - (haveBorrowed ? 1 : 0);
                    haveBorrowed = false;
                    result.internalNumbers.Add(temporarySubtraction);
                }
            }

            return result;
        }

        private IntegerNumber MultiplyAbs(IntegerNumber another) {
            IntegerNumber result = new IntegerNumber();
            IntegerNumber temp; // = this;

            for (int i = 0; i < another.internalNumbers.Count; i++) {
                temp = this.MultiplyAbs(another.internalNumbers[i]);

                for (int j = 0; j < i; j++) {
                    // equivalent to: temp = temp * internalBase
                    temp.internalNumbers.Insert(0, 0);
                }

                //result += temp;
                result = result.AddAbs(temp);
            }

            return result;
        }

        private IntegerNumber DivideAbs(IntegerNumber another, out IntegerNumber module) {
            IntegerNumber result = new IntegerNumber();
            module = new IntegerNumber();
            IntegerNumber tempMultiplication = new IntegerNumber();
            IntegerNumber tempSubtraction = this;

            if (this.IsAbsSmallerThan(another)) {
                module = another;
            } else if (this.IsAbsGreaterThan(another)) {
                do {
                    tempSubtraction = tempSubtraction.SubtractAbs(another);
                    //result++;
                    result = result.AddAbs(new IntegerNumber(1));
                } while (tempSubtraction > zero);

                module = this.SubtractAbs(another.MultiplyAbs(result));
            } else {    // |this| == |another|
                //result++;
                result = result.AddAbs(new IntegerNumber(1));
            }

            return result;
        }

        private bool IsAbsGreaterThan(IntegerNumber another) {
            // a > b --> b < a
            return another.IsAbsSmallerThan(this);
        }

        private bool IsAbsSmallerThan(IntegerNumber another) {
            // check if the numbers are equal
            if (this != another) {      
                // the numbers are not equal
                // check if the internal numbers collection length of this IntegerNumber is smaller than the other's
                if (this.internalNumbers.Count < another.internalNumbers.Count) {
                    return true;
                } else if (this.internalNumbers.Count == another.internalNumbers.Count) {
                    // the lengths are equal
                    // compare each number in the collection in an ordered fashion
                    for (int i = this.internalNumbers.Count - 1; i >= 0; i++) {
                        if (this.internalNumbers[i] < another.internalNumbers[i]) {
                            return true;
                        } else if (this.internalNumbers[i] > another.internalNumbers[i]) {
                            return false;
                        }
                    }
                }
            }

            return false;
        }
        
        /// <summary>
        /// The russian algorithm for the multiplication.
        /// </summary>
        /// <param name="another">
        /// The other number with which this IntegerNumber 
        /// must be multiplied.
        /// </param>
        /// <returns>
        /// The result of the multiplication of this 
        /// IntegerNumber with another number.
        /// </returns>
        private IntegerNumber MultiplyAbs(long another) {
            if (another == 0) {
                return new IntegerNumber();
            } else if (another == 1) {
                return new IntegerNumber(1);
            }

            IntegerNumber result = new IntegerNumber();
            IntegerNumber temp = this;

            while (another > 1) {
                if (another % 2 == 1) {
                    //result += temp;
                    result = result.AddAbs(temp);
                }

                //temp += temp;
                temp = temp.AddAbs(temp);
                another /= 2;
            } 

            return result + temp;
        }
        #endregion
        #endregion

        #region operators
        #region mathematical operators
        public static IntegerNumber operator +(IntegerNumber first, IntegerNumber second) {
            return first.Add(second);
        }

        public static IntegerNumber operator -(IntegerNumber first, IntegerNumber second) {
            return first.Subtract(second);
        }

        public static IntegerNumber operator *(IntegerNumber first, IntegerNumber second) {
            return first.Multiply(second);
        }

        public static IntegerNumber operator /(IntegerNumber first, IntegerNumber second) {
            return first.Divide(second);
        }

        public static IntegerNumber operator ++(IntegerNumber toIncrement) {
            return toIncrement + new IntegerNumber(1);
        }

        public static IntegerNumber operator --(IntegerNumber toDecrement) {
            return toDecrement - new IntegerNumber(1);
        }
        #endregion

        #region boolean operators
        public static bool operator ==(IntegerNumber first, IntegerNumber second) {
            return first.Equals(second);
        }

        public static bool operator !=(IntegerNumber first, IntegerNumber second) {
            return !(first == second);
        }

        public static bool operator <(IntegerNumber first, IntegerNumber second) {
            bool isFirstSmallerThanSecond = false;

            if (first.sign == second.sign) {
                isFirstSmallerThanSecond = first.IsAbsSmallerThan(second);

                if (first.sign == Signs.Negative) {
                    isFirstSmallerThanSecond = !isFirstSmallerThanSecond;
                }
            } else {
                if (first.sign == Signs.Negative) {
                    isFirstSmallerThanSecond = true;
                }
            }

            return isFirstSmallerThanSecond;
        }

        public static bool operator >(IntegerNumber first, IntegerNumber second) {
            bool isFirstGreaterThanSecond = false;

            if (first.sign == second.sign) {
                isFirstGreaterThanSecond = first.IsAbsGreaterThan(second);

                if (first.sign == Signs.Negative) {
                    isFirstGreaterThanSecond = !isFirstGreaterThanSecond;
                }
            } else {
                if (first.sign == Signs.Positive) {
                    isFirstGreaterThanSecond = true;
                }
            }

            return isFirstGreaterThanSecond;
        }

        public static bool operator <=(IntegerNumber first, IntegerNumber second) {
            return first < second || first == second;
        }

        public static bool operator >=(IntegerNumber first, IntegerNumber second) {
            return first > second || first == second;
        }
        #endregion

        #region conversion operators
        public static explicit operator int(IntegerNumber source) {
            throw new NotImplementedException();
        }

        //public static implicit operator IntegerNumber(int source) {
        //    return new IntegerNumber(source);
        //}

        public static implicit operator IntegerNumber(string source) {
            if (!(new Regex(@"^[+-]{0,1}\d+$").IsMatch(source))) {
                throw new ArgumentException(
                    "The given value is not in a correct format. It should be the sign followed by the numbers (i.e. 123, -251, +20, etc...).",
                    "value");
            }

            IntegerNumber result = new IntegerNumber();
            int index = 0;

            if (source.Contains("-")) {
                result.sign = Signs.Negative;
                index++;
            } else if (source.Contains("+")) {
                index++;
            }

            char[] chars = source.Substring(index).ToCharArray();
            IntegerNumber multiplier = new IntegerNumber(1);
            int charIntValue;
            char c;

            IntegerNumber temp;

            //foreach (char c in chars) {
            for(int i = chars.Length - 1; i >= 0; i--) {
                c = chars[i];

                if (CharValueMapping.ContainsKey(c)) {
                    charIntValue = CharValueMapping[c];
                } else {
                    throw new ArgumentException("The given value is not in a correct format. There is a char not in the range [0, 10]");
                }

                if (charIntValue > 10) {
                    throw new ArgumentException("The given value is not in a correct format. There is a char not in the range [0, 10]");
                }

                temp = multiplier.MultiplyAbs(charIntValue);
                //result = result.AddAbs(multiplier.MultiplyAbs(charIntValue));
                result = result.AddAbs(temp);
                multiplier = multiplier.MultiplyAbs(10);
            }
            return result;
        }
        #endregion
        #endregion
    }
}
